package org.visionlibrary.image.geomtric.model;

import java.util.ArrayList;
import java.util.List;

import org.visionlibrary.image.geomtric.util.Pair;
import org.visionlibrary.image.geomtric.util.Utils;


public class Vector2d {
	protected Point2d first = null; // p0
	protected Point2d last = null; // u

	protected List<Point2d> points = null;

	public Vector2d(double xf, double yf, double xl, double yl) {
		this(new Point2d(xf, yf), new Point2d(xl, yl));
	}

	public Vector2d(Point2d first, Point2d last) {
		points = new ArrayList<Point2d>();
		points.add(first);
		points.add(last);

		this.first = first;
		this.last = last;
	}

	public Vector2d(Vector2d vector) {
		this.points = new ArrayList<Point2d>();
		this.points.addAll(vector.points);

		this.first = vector.first;
		this.last = vector.last;
	}

	public Vector2d(List<Point2d> points) {
		this.points = new ArrayList<Point2d>();
		this.points.addAll(points);

		this.first = points.get(0);
		this.last = points.get(points.size() - 1);
	}

	public void setFirst(Point2d first) {
		this.first = first;
	}
	
	public Point2d getFirst() {
		return first;
	}

	public void setLast(Point2d last) {
		this.last = last;
	}
	
	public Point2d getLast() {
		return last;
	}

	public void add(Point2d p) {
		points.add(new Point2d(p));
		last = points.get(points.size() - 1);
	}

	public int size() {
		return points.size();
	}

	public Point2d get(int index) {
		return points.get(index);
	}

	public void setPoints(List<Point2d> points) {
		this.points = points;
	}
	
	public List<Point2d> getPoints() {
		return points;
	}

	public void clearPoints() {
		this.points.clear();
	}
	
	public static Vector2d copy(Vector2d vector) {
		List<Point2d> pcopy = new ArrayList<Point2d>();
		for (int i = 0; i < vector.points.size(); i++)
			pcopy.add(Point2d.copy(vector.get(i)));

		return new Vector2d(pcopy);
	}

	@Override
	public String toString() {
		return "Vector2d: (" + first.getX() + ":" + first.getY() + ") => ("
				+ last.getX() + ":" + last.getY() + ")  total points: "
				+ points.size() + " length: " + Point2d.distance(first, last)
				+ " angle: " + angle();
	}

	public Point2d middle() {
		return Point2d.middle(first, last);
	}

	// dlugosc wektora w matematycznym rozumieniu
	public double length() {
		return Point2d.distance(first, last);
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof Vector2d) {
			Vector2d v = (Vector2d) obj;
			return this.first.equals(v.first) && this.last.equals(v.last);
		}
		return false;
	}

	public Point2d coordinates() {
		return new Point2d(last.getX() - first.getX(), last.getY()
				- first.getY());
	}

	public static double distance(double A, double B, double C, Point2d p) {
		return Math.abs(A * p.getX() + B * p.getY() + C)
				/ Math.sqrt(A * A + B * B);
	}

	public static double distance(Vector2d vector1, Vector2d vector2) {
		double v1x = vector1.first.getX() - vector2.first.getX();
		double v2x = vector1.last.getX() - vector2.last.getX();
		double v1y = vector1.first.getY() - vector2.first.getY();
		double v2y = vector1.last.getY() - vector2.last.getY();
		return Math.sqrt((v1x * v1x) + (v1y * v1y) + (v2x * v2x) + (v2y * v2y));
	}

	public static Vector2d scale(Vector2d segment, double ratio) {
		List<Point2d> points = new ArrayList<Point2d>();
		for (Point2d point : segment.points) {
			points.add(Point2d.multiply(point, ratio));
		}
		Vector2d scaled = new Vector2d(points);
		return scaled;
	}
	
	public static Vector2d move(Vector2d line, double dx, double dy) {
		List<Point2d> points = new ArrayList<Point2d>();
		for (Point2d point : line.points) {
			points.add(Point2d.move(point, dx, dy));
		}

		Vector2d moved = new Vector2d(points);

		return moved;
	}
	
	public double distance(Vector2d vector) {
		return Vector2d.distance(this, vector);
	}

	public double angle(boolean fullAngle) {
		return Vector2d.angle(first, last, fullAngle);
	}

	public double angle() {
		return this.angle(false);
	}

	/**
	 * Wyznacza kat w kierunku wektora wznaczonego przez punkty p1 i p2
	 * 
	 * Kat jest z przedzialu <0, 180> stopni
	 * 
	 * @param p1
	 *            punkt w 2D
	 * @param p2
	 *            punkt w 2D
	 * @param fullAngle
	 *            informacja czy kat ma byc zwracany jako 0 .. 360 czy 0 .. 180.
	 *            Parametr ustawiony na true spowoduje zwrocenie kata w
	 *            przedziale 0 do 360 stopni.
	 * @return
	 */
	public static double angle(Point2d p1, Point2d p2, boolean fullAngle) {
		double x = p2.getX() - p1.getX();
		double y = p2.getY() - p1.getY();
		double angel = Math.acos(x / Math.sqrt(x * x + y * y));
		if (y < 0) {
			angel = 2.0 * Math.PI - angel;
		}

		if (!fullAngle)
			if (angel >= Math.PI)
				angel -= Math.PI;

		return angel;
	}

	public Vector2d getNormalized() {
		Point2d co = this.coordinates();
		return new Vector2d(0, 0, co.getX() / this.length(), co.getY()
				/ this.length());
	}

	public static double dotProduct(Vector2d v1, Vector2d v2) {
		Point2d vc1 = v1.coordinates();
		Point2d vc2 = v2.coordinates();

		return vc1.getX() * vc2.getX() + vc1.getY() * vc2.getY();
	}

	public static boolean isOrthogonal(Vector2d v1, Vector2d v2) {
		return Double.compare(Vector2d.dotProduct(v1, v2), 0d) == 0;
	}

	public static boolean isParallel(Vector2d v1, Vector2d v2) {
		double a1 = v1.angle(true);
		double a2 = v2.angle(true);

		double a = a1 - a2;
		double s = Math.sin(a);

		return Double.compare(Utils.roundDouble(s, 10), Utils.roundDouble(0.0d,
				10)) == 0;
	}

	public Point2d compute(double t) {
		return compute(t, 2);
	}

	public Point2d compute(double t, int precision) {
		double x = first.getX() + t * (last.getX() - first.getX());
		double y = first.getY() + t * (last.getY() - first.getY());
		x = Utils.roundDouble(x, precision);
		y = Utils.roundDouble(y, precision);
		return new Point2d(x, y);
	}

	public Pair<Vector2d, Vector2d> orthogonal(int x, int y) {
		throw new UnsupportedOperationException();
	}
}
